package top.maof.lightsearch.core;

import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.*;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.wltea.analyzer.lucene.IKAnalyzer;
import top.maof.lightsearch.handle.Entity;
import top.maof.lightsearch.handle.HandleChain;
import top.maof.lightsearch.handle.HighlighterHandle;
import top.maof.lightsearch.handle.Index;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Slf4j
public final class LightSearch {

    // 创建目录对象
    private Directory directory;

    // 创建配置对象
    private IndexWriterConfig conf;

    // 创建索引写出工具
    private IndexWriter indexWriter;


    // IK分词器,默认分词器
    private final Analyzer analyzer = new IKAnalyzer();

    public Analyzer analyzer() {
        return analyzer;
    }

    public LightSearch(String path) {
        try {
            this.directory = FSDirectory.open(new File(path).toPath());
            this.startWrite(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public LightSearch(String path, IndexWriterConfig.OpenMode mode) {
        try {
            this.directory = FSDirectory.open(new File(path).toPath());
            this.startWrite(mode);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    private void startWrite(IndexWriterConfig.OpenMode mode) {
        this.conf = new IndexWriterConfig(this.analyzer);
        conf.setOpenMode(mode);
        try {
            this.indexWriter = new IndexWriter(directory, conf);
            this.searcherManager = new SearcherManager(this.indexWriter, new SearcherFactory());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static LightSearch getInstance(String path) {
        return new LightSearch(path);
    }


    private IndexSearcher getIndexSearcher() {
        IndexSearcher indexSearcher = null;
        try {
            // 这个方法同DirectoryReader.openIfChanged(dirReader)效果一样，其实底层还是调用的该方法实现的
            this.searcherManager.maybeRefresh();
            // 借用一个IndexSearcher对象的引用，记住该对象用完之后要归还的，有借有还再借不难
            indexSearcher = searcherManager.acquire();
        } catch (IOException e) {
            log.error("刷新搜索对象失败");
            e.printStackTrace();
        }
        return indexSearcher;
    }


    private SearcherManager searcherManager;


    private void closeIndexSearcher(IndexSearcher indexSearcher) throws IOException {
        if (indexSearcher != null) {
            searcherManager.release(indexSearcher);
            //归还从SearcherManager处借来的IndexSearcher对象
        }
    }

    @Deprecated
    public <T> List<T> search(Class<T> tClass, Query query, int n) {
        IndexSearcher search = getIndexSearcher();
        List<T> list = new ArrayList<>();
        try {
            TopDocs topDocs = search.search(query, n);
            HandleChain handleChain = new HandleChain();
            ScoreDoc[] scoreDocs = topDocs.scoreDocs;
            Index index = new Index();
            index.setQuery(query);
            index.setAnalyzer(this.analyzer);
            for (ScoreDoc scoreDoc : scoreDocs) {
                Entity<T> entity = new Entity<>(tClass);
                index.setDocument(search.doc(scoreDoc.doc));
                handleChain.doSearch(entity, index);
                list.add(entity.getT());
            }
            closeIndexSearcher(search);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return list;
    }


    public <T> ResultPage<T> search(Class<T> tClass, Query query, int page, int pageSize) {
        IndexSearcher search = getIndexSearcher();
        List<T> list = new ArrayList<>(pageSize);
        TopDocs topDocs = null;
        try {
            topDocs = this.search(query, page, pageSize);
            HandleChain handleChain = new HandleChain();
            ScoreDoc[] scoreDocs = topDocs.scoreDocs;
            Index index = new Index();
            index.setQuery(query);
            index.setAnalyzer(this.analyzer);
            for (ScoreDoc scoreDoc : scoreDocs) {
                Entity<T> entity = new Entity<>(tClass);
                index.setDocument(search.doc(scoreDoc.doc));
                handleChain.doSearch(entity, index);
                list.add(entity.getT());
            }
            closeIndexSearcher(search);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return new ResultPage<T>(list, topDocs.totalHits);
    }


    /**
     * 使用注解中的高亮标签
     * <p>
     * 注意：若是需要高亮的字段不符合要求，
     * 高亮处理器HighlighterHandle将不生效或者报错
     */
    @Deprecated
    public <T> List<T> searchHighlighter(Class<T> tClass, Query query, int n) {

        IndexSearcher search = getIndexSearcher();
        List<T> list = new ArrayList<>();
        try {
            TopDocs topDocs = search.search(query, n);
            HandleChain handleChain = new HandleChain();
            // HighlighterHandle不可共享，需要实例化
            handleChain.add(new HighlighterHandle());
            ScoreDoc[] scoreDocs = topDocs.scoreDocs;
            Index index = new Index();
            index.setQuery(query);
            index.setAnalyzer(this.analyzer);
            for (ScoreDoc scoreDoc : scoreDocs) {
                Document document = search.doc(scoreDoc.doc);
                Entity<T> entity = new Entity<>(tClass);
                // 设置新的document
                index.setDocument(document);
                handleChain.doSearch(entity, index);
                list.add(entity.getT());
            }
            closeIndexSearcher(search);

        } catch (Exception e) {
            e.printStackTrace();
        }

        return list;
    }


    public <T> ResultPage<T> searchHighlighter(Class<T> tClass, Query query, int page, int pageSize) {

        IndexSearcher search = getIndexSearcher();
        List<T> list = new ArrayList<>();
        TopDocs topDocs = null;
        try {
            topDocs = this.search(query, page, pageSize);
            HandleChain handleChain = new HandleChain();
            // HighlighterHandle不可共享，需要实例化
            handleChain.add(new HighlighterHandle());
            ScoreDoc[] scoreDocs = topDocs.scoreDocs;
            Index index = new Index();
            index.setQuery(query);
            index.setAnalyzer(this.analyzer);

            for (ScoreDoc scoreDoc : scoreDocs) {
                Entity<T> entity = new Entity<>(tClass);
                index.setDocument(search.doc(scoreDoc.doc));
                handleChain.doSearch(entity, index);
                list.add(entity.getT());
            }

            closeIndexSearcher(search);

        } catch (Exception e) {
            e.printStackTrace();
        }

        return new ResultPage<>(list, topDocs.totalHits);
    }


    public <T> ResultPage<T> searchHighlighter(Class<T> tClass, Query query, String preTag, String postTag,
                                               int page, int pageSize) {
        IndexSearcher search = getIndexSearcher();
        List<T> list = new ArrayList<>();
        TopDocs topDocs = null;
        try {
            topDocs = this.search(query, page, pageSize);
            HandleChain handleChain = new HandleChain();
            // HighlighterHandle不可共享，需要实例化
            handleChain.add(new HighlighterHandle(preTag, postTag));
            ScoreDoc[] scoreDocs = topDocs.scoreDocs;
            Index index = new Index(null, this.analyzer, query);

            for (ScoreDoc scoreDoc : scoreDocs) {
                Entity<T> entity = new Entity<>(tClass);
                index.setDocument(search.doc(scoreDoc.doc));
                handleChain.doSearch(entity, index);
                list.add(entity.getT());
            }
            closeIndexSearcher(search);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return new ResultPage<>(list, topDocs.totalHits);
    }


    /**
     * 该方法传入的高亮标签可以覆盖在实体类中配置的标签
     * <p>
     * 注意：
     * 若是需要高亮的字段不符合要求，高亮处理器HighlighterHandle将不生效或者报错
     */
    @Deprecated
    public <T> List<T> searchHighlighter(Class<T> tClass, Query query, String preTag, String postTag, int n) {

        IndexSearcher search = getIndexSearcher();
        List<T> list = new ArrayList<>();
        try {
            TopDocs topDocs = search.search(query, n);
            HandleChain handleChain = new HandleChain();
            // HighlighterHandle不可共享，需要实例化
            handleChain.add(new HighlighterHandle(preTag, postTag));
            ScoreDoc[] scoreDocs = topDocs.scoreDocs;
            Index index = new Index(null, this.analyzer, query);
            for (ScoreDoc scoreDoc : scoreDocs) {
                int doc = scoreDoc.doc;
                Document document = search.doc(doc);
                Entity<T> entity = new Entity<>(tClass);
                // 设置新的document
                index.setDocument(document);
                handleChain.doSearch(entity, index);
                list.add(entity.getT());
            }
            closeIndexSearcher(search);

        } catch (Exception e) {
            e.printStackTrace();
        }

        return list;
    }


    private TopDocs search(Query query, int page, int pageSize)
            throws Exception {
        IndexSearcher search = getIndexSearcher();
        int start = (page - 1) * pageSize;
        if (0 == start) {
            TopDocs topDocs = search.search(query, page * pageSize);
            return topDocs;
        }
        // 查询数据， 结束页面自前的数据都会查询到，但是只取本页的数据
        TopDocs topDocs = search.search(query, start);
        // 获取到上一页最后一条

        ScoreDoc preScore = topDocs.scoreDocs[start - 1];
        //查询最后一条后的数据的一页数据
        topDocs = search.searchAfter(preScore, query, pageSize);
        return topDocs;
    }


    public <E> void write(List<E> list) throws Exception {
        if (list == null || list.isEmpty()) {
            return;
        }
        List<Document> docs = new ArrayList<>(list.size());
        for (E e : list) {
            // 无法共享，使用时必须实例化
            HandleChain handleChain = new HandleChain();
            Document document = new Document();
            Entity<E> entity = new Entity<E>(e);
            Index index = new Index(document);
            handleChain.doIndex(entity, index);
            docs.add(index.getDocument());
        }
        this.indexWriter.addDocuments(docs);
    }

    public <E> void write(E e) throws Exception {
        HandleChain handleChain = new HandleChain();
        Entity<E> entity = new Entity(e);

        Index index = new Index(new Document());
        handleChain.doIndex(entity, index);
        this.indexWriter.addDocument(index.getDocument());
    }


    public void commit() {
        try {
            this.indexWriter.commit();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public void merge(int maxNumSegments) {
        try {
            this.indexWriter.forceMerge(maxNumSegments);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public boolean delete(Query query) {
        try {
            return this.indexWriter.deleteDocuments(query) > 0;
        } catch (IOException e) {
            log.error("索引删除失败: {}", query.toString());
        }
        return false;
    }

    public boolean delete(Term... terms) {
        try {
            return this.indexWriter.deleteDocuments(terms) > 0;
        } catch (IOException e) {
            log.error("索引删除失败: {}", terms);
        }
        return false;
    }

    public <T> boolean update(T t, Term... terms) {
        try {
            this.delete(terms);
            this.write(t);
            return true;
        } catch (Exception e) {
            log.error("索引修改失败: {}", terms);
        }
        return false;
    }


    public <T> boolean update(T t, Query query) {
        try {
            this.delete(query);
            this.write(t);
            return true;
        } catch (Exception e) {
            log.error("索引修改失败: {}", t);
        }
        return false;
    }


    public void close() {
        try {
            if (this.searcherManager != null)
                this.searcherManager.close();

            if (this.indexWriter != null)
                this.indexWriter.commit();

            if (this.indexWriter != null)
                indexWriter.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}
